#pragma once

#include "funpl.h"
#include <lean/memory/chunk_heap.h>
#include <lean/tags/noncopyable.h>
#include <vector>

namespace funpl
{

namespace ast
{

/// Node type enumeration.
namespace node_type
{
	enum t
	{
		module,
		import,
		module_name,

		namespace_,

		function,
		struct_,
		class_,
//		instantiate, TODO
		typegroup,
		typedef_,
		parameters,
		metaparameters,

		type_identifier,
		inline_metatype,
		metaarguments,
		pointer_type,
		array_type,
		function_type,

		metatype,
		metavariable,

		variable,

		compound,

		if_statement,
		for_statement,
		while_statement,
		do_while_statement,
		throw_statement,
		return_statement,
		try_statement,
		catch_statement,

		new_expression,
		delete_expression,

		guard_expression,
		unguard_expression,

		identifier,
		type_expression,
		function_expression,

		metaevaluate,
		evaluate,
		arguments,

		int_literal,
		uint_literal,
		long_literal,
		ulong_literal,
		float_literal,

		char_literal,
		string_literal,

		unary_operator,
		binary_operator,
		type_cast
	};
}

/// Abstract syntax tree node.
struct node
{
	const node_type::t nodeType;
	node *parent;

	/// Constructor.
	node(node_type::t type, node *parent = nullptr)
		: nodeType(type),
		parent(parent) { }
	virtual ~node() { }
};

/// Reparents the given node.
template <class Node>
LEAN_INLINE Node* reparent(Node *child, node *parent)
{
	if (child)
		child->parent = parent;
	return child;
}

/// Abstract syntax tree.
class tree : public lean::noncopyable
{
private:
	lean::chunk_heap<sizeof(node) * 128> m_pool;
	std::vector<node*> m_nodes;

	template <class Node>
	LEAN_NOINLINE Node* secure_node(Node *node)
	{
		try
		{
			m_nodes.push_back(node);
		}
		catch(...)
		{
			node->~Node();
			throw;
		}

		return node;
	}

public:
	node *root;
	bool valid;

	/// Constructor.
	tree()
		: root(),
		valid(true) { }
	/// Destructor.
	~tree()
	{
		clear();
	}

	/// Allocates an object.
	template <class Value>
	Value* allocate() { return new (m_pool.allocate(sizeof(Value))) Value(); }
	/// Allocates an object.
	template <class Value, class T1>
	Value* allocate(T1 &&p1) { return new (m_pool.allocate(sizeof(Value))) Value(std::forward<T1>(p1)); }
	/// Allocates an object.
	template <class Value, class T1, class T2>
	Value* allocate(T1 &&p1, T2 &&p2) { return new (m_pool.allocate(sizeof(Value))) Value(std::forward<T1>(p1), std::forward<T2>(p2)); }
	/// Allocates an object.
	template <class Value, class T1, class T2, class T3>
	Value* allocate(T1 &&p1, T2 &&p2, T3 &&p3) { return new (m_pool.allocate(sizeof(Value))) Value(std::forward<T1>(p1), std::forward<T2>(p2), std::forward<T3>(p3)); }

	/// Allocates a node.
	template <class Node>
	Node* create_node() { return secure_node( allocate<Node>() ); }
	/// Allocates a node.
	template <class Node, class T1>
	Node* create_node(T1 &&p1) { return secure_node( allocate<Node>(p1) ); }
	/// Allocates a node.
	template <class Node, class T1, class T2>
	Node* create_node(T1 &&p1, T2 &&p2) { return secure_node( allocate<Node>(p1, p2) ); }
	/// Allocates a node.
	template <class Node, class T1, class T2, class T3>
	Node* create_node(T1 &&p1, T2 &&p2, T3 &&p3) { return secure_node( allocate<Node>(p1, p2, p3) ); }

	/// Clears the tree.
	LEAN_NOINLINE void clear()
	{
		valid = true;
		root = nullptr;

		for (auto it = m_nodes.begin(); it != m_nodes.end(); ++it)
		{
			if (*it)
				(*it)->~node();
			*it = nullptr;
		}
		m_nodes.clear();

		m_pool.clear();
	}
};

} // namespace

} // namespace